package com.nn.gui;

import com.gooddies.events.Lookup;
import com.gooddies.swing.ChunkedTextProcessor;
import com.swingson.SwingsonGuiBuilder;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;

/**
 * @author sad
 */
public class MainFrame extends JPanel {

    private final int width = 30;
    private final int height = 30;
    private final int cellSize = 15;
    private ChunkedTextProcessor title;
    private final Receptor[][] receptors = new Receptor[width][height];
    private final List<Seeker> seekers = new ArrayList<Seeker>();
    private static final Color[] grayColors = new Color[256];
    private static final Color[] greenColors = new Color[256];
    private final String X_TITLE_CHANGE = "x";
    private final String Y_TITLE_CHANGE = "y";

    private final float RECEPTOR_ACTIVATION_LIMIT = 0.5f;

    static {
        for (int i = 0; i < 256; i++) {
            grayColors[i] = new Color(i, i, i);
            greenColors[i] = new Color(28, i, 31);
        }
    }

    private final JPanel drawPanel = new JPanel() {

        @Override
        public void paint(Graphics g) {
            super.paint(g);

            for (int j = 0; j < height; j++) {
                for (int i = 0; i < width; i++) {
                    g.setColor(Color.BLACK);
                    if (receptors[i][j].getBaseActivation() > 0.8) {
                        g.setColor(Color.LIGHT_GRAY);
                        g.fillRect(i * cellSize, j * cellSize, cellSize, cellSize);
                        g.setColor(Color.BLACK);
                    }

                    g.drawRect(i * cellSize, j * cellSize, cellSize, cellSize);
                    int crycolor = (int) (receptors[i][j].getCurrentActivation() * 255);
                    g.setColor(grayColors[crycolor]);
                    g.fillRect(i * cellSize + 5, j * cellSize + 5, cellSize - 10, cellSize - 10);
                }
            }

            seekers.stream().forEach((seeker) -> {
                drawSeeker(g, seeker);
            });
        }
    };

    public MainFrame(JFrame owner) {
        init(owner);
    }

    private void init(final JFrame owner) {
        title = new ChunkedTextProcessor((String text) -> {
            owner.setTitle(text);
        });

        Lookup.getDefault().addChangeEvent(X_TITLE_CHANGE, (String newValue) -> {
            title.setTitle(X_TITLE_CHANGE, newValue);
        }).addChangeEvent(Y_TITLE_CHANGE, (String newValue) -> {
            title.setTitle(Y_TITLE_CHANGE, newValue);
        });
        title.setTitle("program_name", "NN Prototype.");
        title.setTitle("x", "");
        title.setTitle("y", "");
        SwingsonGuiBuilder.createGuiFromJsonInPackage(this);
        //activateRandomNeurons();
        initSeekers();
        onPattern1Selected();
    }

    private void initSeekers() {
        int seekerCount = 20;
        float maxPriority = 4000;
        float deltaP = maxPriority / seekerCount;
        float currentPriority = maxPriority;
        for (int i = 0; i < seekerCount; i++) {
            Seeker seeker = new Seeker();
            seeker.setX(460);
            seeker.setY(i * cellSize);
            seeker.setAx(seeker.getX() + cellSize / 2);
            seeker.setAy(seeker.getY() + cellSize / 2);
            seeker.setPriority(currentPriority);
            currentPriority -= deltaP;
            seekers.add(seeker);
        }
    }

    private void clearNeuronsTempActivation() {
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                Receptor r = receptors[i][j];
                r.setCurrentActivation(0);
                r.setNegative(false);
                r.setPriority(-9999999);
            }
        }
        for (Seeker seeker : seekers) {
            seeker.setActivation(0);
        }
    }

    private void clearNeuronsActivation() {
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                Receptor r = receptors[i][j];
                r.setBaseActivation(0);
                r.setNegative(false);
                r.setPriority(-9999999);
            }
        }
        for (Seeker seeker : seekers) {
            seeker.setActivation(0);
        }
    }

    private List<N> searchForActiveNeurons() {
        List<N> res = new ArrayList<>();
        for (int j = 0; j < height; j++) {
            for (int i = 0; i < width; i++) {
                Receptor r = receptors[i][j];
                if (r.getBaseActivation() >= RECEPTOR_ACTIVATION_LIMIT) {
                    N n = new N(i, j, r);
                    n.setxPos(i * cellSize + cellSize / 2);
                    n.setyPos(j * cellSize + cellSize / 2);
                    res.add(n);
                }
            }
        }

        return res;
    }

    private void processSeekers() {
        List<N> activeNeurons = searchForActiveNeurons();
        seekers.stream()
                .sorted((s1, s2) -> (int) (s1.getPriority() - s2.getPriority()))
                .forEach((seeker) -> {
                    processSeeker(seeker, activeNeurons);
                });
    }

    private N findBestAttractorNeuron(Seeker seeker, List<N> activeNeurons) {
        float minlength = 999999999;
        N bestN = null;
        for (N n : activeNeurons) {
            float currentLength = length(n, seeker.getAx(), seeker.getAy());
            if (currentLength < minlength) {
                bestN = n;
                minlength = currentLength;
            }
        }

        return bestN;
    }

    private float getSeekerBaseActivation(Seeker seeker) {
        int activeCount = 1;
        for (N n : seeker.getConnected()) {
            float a = n.getR().getBaseActivation();
            if (a >= RECEPTOR_ACTIVATION_LIMIT) {
                activeCount++;
            }
        }

        int size=seeker.getConnected().size();
        int negCount=(int)((size-activeCount)*1.5);
        float summActivation = (activeCount-negCount) / size;
        if (summActivation > 0.7) {
            //нужно пройтись по всем подключенным рецепторам и понизить их уровень активации
            for (N n : seeker.getConnected()) {
                Receptor r = n.getR();
                float ca = r.getCurrentActivation() / 3.0f;
                r.setCurrentActivation(ca);
                if(r.getCurrentPriority()<seeker.getPriority()){
                    r.setPriority(seeker.getPriority());
                }
            }

            return 1;
        }

        return 0;
    }

    private void processSeeker(Seeker seeker, List<N> activeNeurons) {
        float activation=getSeekerBaseActivation(seeker);
        
        
        final N bestAttractor = findBestAttractorNeuron(seeker, activeNeurons);
        if (bestAttractor == null) {
            return;
        }

        float axonMove = 10;
        float l = length(bestAttractor, seeker.getAx(), seeker.getAy());
        if (axonMove > l) {
            axonMove = l;
        }

        float x = seeker.getAx();
        float y = seeker.getAy();
        float destX = bestAttractor.getxPos();
        float destY = bestAttractor.getyPos();
        double angle = Math.atan2(destY - y, destX - x);
        float resX = (float) (Math.cos(angle) * axonMove + x);
        float resY = (float) (Math.sin(angle) * axonMove + y);
        seeker.setAx(resX);
        seeker.setAy(resY);

        l = length(bestAttractor, seeker.getAx(), seeker.getAy());
        if (l < 0.00001) {//axon is reached other neuron
            seeker.addConnectedPoint(bestAttractor);
        }
    }

    private void drawSeeker(Graphics g, Seeker seeker) {
        g.fillRect(seeker.getX(), seeker.getY(), cellSize, cellSize);
        g.setColor(Color.BLACK);
        g.drawRect(seeker.getX(), seeker.getY(), cellSize, cellSize);

        g.setColor(seeker.getColor());
        float lastX = seeker.getX() + cellSize / 2;
        float lastY = seeker.getY() + cellSize / 2;
        for (int i = 0; i < seeker.getConnected().size(); i++) {
            N n = seeker.getConnected().get(i);
            g.drawLine((int) lastX + seeker.getDeltaX(), (int) lastY + seeker.getDeltaY(), (int) n.getxPos() + seeker.getDeltaX(), (int) n.getyPos() + seeker.getDeltaY());
            g.fillOval((int) n.getxPos() - 1, (int) n.getyPos() - 1, 2, 2);
            lastX = n.getxPos();
            lastY = n.getyPos();
        }

        g.drawLine((int) lastX + seeker.getDeltaX(), (int) lastY + seeker.getDeltaY(), (int) seeker.getAx() + seeker.getDeltaX(), (int) seeker.getAy() + seeker.getDeltaY());
        g.fillOval((int) seeker.getAx() - 1, (int) seeker.getAy() - 1, 2, 2);
    }

    private float length(N n, float x, float y) {
        float tx = n.getxPos() - x;
        float ty = n.getyPos() - y;
        return (float) Math.sqrt(tx * tx + ty * ty);
    }

    protected void onKeyPressed(char c) {
        processSeekers();
        repaint();
    }

    protected void onMouseMoved(Point p) {
        Lookup.getDefault()
                .put(X_TITLE_CHANGE, " X = " + p.x / cellSize)
                .put(Y_TITLE_CHANGE, " Y = " + p.y / cellSize);
    }

    protected void onMousePressed() {
        drawPanel.requestFocus();
    }

    private void onPattern1Selected() {
        clearNeuronsActivation();
        for (int i = 3; i < 25; i += 2) {
            receptors[i][7].setBaseActivation(1);
        }
        clearNeuronsTempActivation();
        repaint();
    }

    private void onPattern2Selected() {
        clearNeuronsActivation();
        for (int i = 3; i < 25; i += 2) {
            receptors[i][20].setBaseActivation(1);
        }
        clearNeuronsTempActivation();
        repaint();
    }

    private void onPattern3Selected() {
        clearNeuronsActivation();
        for (int i = 2; i < 25; i += 2) {
            receptors[11][i].setBaseActivation(1);
        }
        clearNeuronsTempActivation();
        repaint();
    }
}
